﻿Imports System.Net
Imports System.Net.Sockets

Public Class Manager
    Private Sub New()
    End Sub

    Private Shared mListener As Listener
    Private Shared mReconnectThread As Threading.Thread

    Public Shared Sub Start()
        If Settings.Listen AndAlso mListener Is Nothing Then
            mListener = New Listener
            mListener.Start()
        End If
        If Settings.AutoConnect Then StartAutoConnect()
    End Sub

    Private Shared Sub StartAutoConnect()
        If mReconnectThread Is Nothing Then
            mReconnectThread = New System.Threading.Thread(AddressOf ReconnectWorker)
            mReconnectThread.Priority = Threading.ThreadPriority.BelowNormal
            mReconnectThread.Name = "Reconnect Worker Thread"
            mReconnectThread.IsBackground = True
            mReconnectThread.Start()
        End If
    End Sub
    Private Shared Sub StopAutoConnect()
        If mReconnectThread IsNot Nothing Then
            mReconnectThread.Abort()
            mReconnectThread = Nothing
        End If
    End Sub

    Public Shared Sub [Stop]()
        StopAutoConnect()

        If mListener IsNot Nothing Then
            mListener.Stop()
            mListener = Nothing
        End If
        For Each clsClient As Client In GetAllClients()
            clsClient.Disconnect()
        Next
        Dim dtGiveUpAt As Date = Now.AddSeconds(10).ToUniversalTime
        While mClients.Count > 0 And Now.ToUniversalTime < dtGiveUpAt
            System.Windows.Forms.Application.DoEvents()
            Threading.Thread.Sleep(100)
        End While
        For Each clsClient As Client In GetAllClients()
            clsClient.CloseAndAbort()
        Next
        SyncLock mClients
            mClients.Clear()
        End SyncLock
    End Sub

    Private Shared Sub ReconnectWorker()
        While True
            Try
                'wait 10 seconds until first connect attempt
                Threading.Thread.Sleep(5000)
                For Each clsBuddy As Entities.Buddy In DataStore.Buddies.GetAll
                    Select Case GetBuddyStatus(clsBuddy)
                        Case BuddyStatus.Offline
                            If Not String.IsNullOrEmpty(clsBuddy.Host) Then
                                StartConnect(clsBuddy, True)
                            End If
                    End Select
                Next
                Threading.Thread.Sleep(10000)
            Catch ex As Threading.ThreadAbortException
                Exit While
            Catch ex As Exception
            End Try
        End While
    End Sub

    Public Shared Sub ReevalSettings()
        If Not Settings.Listen Then
            [Stop]()
        ElseIf mListener Is Nothing Then
            Start()
        ElseIf mListener.CurrentPort <> Settings.Port Then
            [Stop]()
            Start()
        End If
        If Settings.AutoConnect And mReconnectThread Is Nothing Then
            StartAutoConnect()
        ElseIf Not Settings.AutoConnect And mReconnectThread IsNot Nothing Then
            StopAutoConnect()
        End If
    End Sub

    Public Shared Event ClientStatusChanged(ByVal client As Client)
    Friend Shared Sub OnClientStatusChanged(ByVal client As Client)
        Utils.Threading.Invoke(AddressOf OnClientStatusChanged_CallBack, client)
    End Sub
    Private Shared Sub OnClientStatusChanged_CallBack(ByVal client As Client)
        RaiseEvent ClientStatusChanged(client)
    End Sub

    Public Shared Sub IssuePairupPinCode(ByVal buddy As Entities.Buddy, ByVal forEmail As Boolean)
        Randomize()
        Dim sPinCode As String = CInt(100000000 * Rnd()).ToString("00000000")
        buddy.PairUpPinCode = sPinCode
        buddy.PassCode_LoginAtBuddy = Nothing
        buddy.PassCode_LoginHere = Nothing
        If forEmail Then
            buddy.PairUpValidUntil = Now.AddDays(1).ToUniversalTime
        Else
            buddy.PairUpValidUntil = Now.AddMinutes(5).ToUniversalTime
        End If
    End Sub

    Public Shared Function StartConnectAndPairUp(ByVal host As String, ByVal port As Integer, ByVal buddyNr As Integer, ByVal pinCode As String) As Boolean
        Dim clsClient As New Client()
        Manager.AddClient(clsClient)
        clsClient.StartPairUp(host, port, buddyNr, pinCode)
        Return True
    End Function
    Public Shared Sub StartConnect(ByVal buddy As Entities.Buddy, ByVal isAutomatedRetry As Boolean)
        Select Case GetBuddyStatus(buddy)
            Case BuddyStatus.Offline
                Dim clsClient As New Client
                clsClient.Buddy = buddy
                Manager.AddClient(clsClient)
                clsClient.StartConnect(isAutomatedRetry)
        End Select
    End Sub
    Public Shared Sub Disconnect(ByVal buddy As Entities.Buddy)
        For Each clsClient As Client In GetAllClients()
            If clsClient.Buddy.Id = buddy.Id Then
                clsClient.Disconnect()
                Exit For
            End If
        Next
    End Sub


    Public Shared Event ClientException(ByVal client As Client, ByVal exception As Exception)
    Friend Shared Sub OnClientException(ByVal client As Client, ByVal exception As Exception)
        Utils.Threading.Invoke(AddressOf OnClientException_Callback, client, exception)
    End Sub
    Private Shared Sub OnClientException_Callback(ByVal client As Client, ByVal exception As Exception)
        RaiseEvent ClientException(client, exception)
    End Sub

    Public Shared Event ClientLogEvent(ByVal client As Client, ByVal text As String)
    Friend Shared Sub OnClientLogEvent(ByVal client As Client, ByVal text As String)
        Utils.Threading.Invoke(AddressOf OnClientLogEvent_CallBack, client, text)
    End Sub
    Private Shared Sub OnClientLogEvent_CallBack(ByVal client As Client, ByVal text As String)
        RaiseEvent ClientLogEvent(client, text)
    End Sub

    Private Shared mClients As New Generic.List(Of Client)
    Private Shared Function GetAllClients() As Client()
        SyncLock mClients
            Return mClients.ToArray
        End SyncLock
    End Function
    Friend Shared Sub AddClient(ByVal client As Client)
        SyncLock mClients
            mClients.Add(client)
        End SyncLock
    End Sub
    Friend Shared Sub RemoveClient(ByVal client As Client)
        SyncLock mClients
            mClients.Remove(client)
        End SyncLock
    End Sub

    Public Enum BuddyStatus
        Offline
        PairingUp
        PairUpPending
        Online
        Connecting
        Unknown
        Disconnecting
    End Enum

    Public Shared Function GetAllOnline() As Generic.List(Of Client)
        Dim aRet As New Generic.List(Of NetNode.Client)

        For Each clsClient As Client In GetAllClients()
            If clsClient.IsOnline Then aRet.Add(clsClient)
        Next
        Return aRet
    End Function

    Public Shared Function IsBuddyOnline(ByVal buddy As Entities.Buddy) As Boolean
        For Each clsClient As Client In GetAllClients()
            If clsClient.Buddy.Id = buddy.Id Then
                Return clsClient.IsOnline
            End If
        Next
        Return False
    End Function

    Public Shared Function GetOnlineByBuddyId(ByVal buddyId As Integer) As Client
        For Each clsClient As Client In GetAllClients()
            If clsClient.IsOnline AndAlso clsClient.Buddy.Id = buddyId Then
                Return clsClient
            End If
        Next
        Return Nothing
    End Function

    Private Shared Function GetBuddyStatus(ByVal buddy As Entities.Buddy) As BuddyStatus
        If buddy Is Nothing Then Return BuddyStatus.Unknown
        For Each clsClient As Client In GetAllClients()
            If clsClient.Buddy IsNot Nothing AndAlso clsClient.Buddy.Id = buddy.Id Then
                If clsClient.IsDisconnecting Then
                    Return BuddyStatus.Disconnecting
                ElseIf clsClient.IsOnline Then
                    Return BuddyStatus.Online
                ElseIf String.IsNullOrEmpty(buddy.PairUpPinCode) Then
                    Return BuddyStatus.Connecting
                Else
                    Return BuddyStatus.PairingUp
                End If
            End If
        Next
        If String.IsNullOrEmpty(buddy.PairUpPinCode) Then
            Return BuddyStatus.Offline
        Else
            Return BuddyStatus.PairUpPending
        End If
    End Function

    Public Shared Function GetBuddyStatusText(ByVal buddy As Entities.Buddy) As String
        Dim udtStatus As BuddyStatus = GetBuddyStatus(buddy)
        Select Case udtStatus
            Case BuddyStatus.Offline
                Return "Offline"
            Case BuddyStatus.Online
                Return "Online"
            Case BuddyStatus.PairingUp
                Return "Paring up..."
            Case BuddyStatus.PairUpPending
                Return "Pair up pending"
            Case BuddyStatus.Connecting
                Return "Connecting..."
            Case Else
                Return udtStatus.ToString
        End Select
    End Function

    Private Shared mMessageSubscribers As New Generic.Dictionary(Of String, IMessageSubscriber)
    Public Shared Sub RegisterMessageSubscriber(ByVal handler As IMessageSubscriber)
        mMessageSubscribers.Add(handler.Code, handler)
    End Sub

    Friend Shared Sub OnTargetMessage(ByVal client As Client, ByVal message As MessageBlock)
        Dim clsThread As New System.Threading.Thread(AddressOf SendMessageToTarget)
        clsThread.IsBackground = True
        clsThread.Name = "Message thread"
        clsThread.Priority = Threading.ThreadPriority.BelowNormal
        clsThread.Start(New SendMessageParams(client, message))
    End Sub
    Private Class SendMessageParams
        Public Client As Client
        Public Message As MessageBlock
        Public Sub New(ByVal client As Client, ByVal message As MessageBlock)
            Me.Client = client : Me.Message = message
        End Sub
    End Class
    Private Shared Sub SendMessageToTarget(ByVal param As Object)
        Dim clsParams As SendMessageParams = CType(param, SendMessageParams)
        Try
            Dim clsClient As Client = clsParams.Client
            Dim clsMessage As MessageBlock = clsParams.Message

            If mMessageSubscribers.ContainsKey(clsParams.Message.Target) Then
                mMessageSubscribers.Item(clsMessage.Target).IncomingMessage(clsClient, clsMessage)
            Else
                Manager.OnClientLogEvent(clsClient, String.Format("Message to target {0} was discarded!", clsMessage.Target))
            End If
        Catch ex As Exception
            Manager.OnClientException(clsParams.Client, ex)
        End Try
    End Sub


End Class

Public Interface IMessageSubscriber
    ReadOnly Property Code() As String
    Sub IncomingMessage(ByVal client As Client, ByVal message As MessageBlock)
End Interface

